namespace Resharper.Abstractions.CodeDom.Expressions.InvocationExpressions
{
  using System;
  using System.Collections.Generic;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Psi.CSharp.Tree;
  using System.Linq;
  using Resharper.Abstractions.CodeDom.Expressions.LiteralExpressions;
  using Resharper.Abstractions.CodeDom.TreeNodes;

  /// <summary>Defines the <see cref="LiteralExpression"/> class.</summary>
  public class InvocationExpression : Expression
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="LiteralExpression"/> class.</summary>
    /// <param name="invocationExpression">The expression.</param>
    internal InvocationExpression([NotNull] IInvocationExpression invocationExpression) : base(invocationExpression)
    {
    }

    #endregion

    #region Properties

    /// <summary>The literal expression field.</summary>
    [NotNull]
    internal IInvocationExpression InternalInvocationExpression
    {
      get
      {
        return (IInvocationExpression)this.InternalExpression;
      }
    }

    /// <summary>
    /// Gets the invoked.
    /// </summary>
    [CanBeNull]
    public TreeNode InvokedTreeNode
    {
      get
      {
        var reference = this.InternalInvocationExpression.InvokedExpression as IReferenceExpression;
        if (reference == null)
        {
          return null;
        }

        var resolveResult = reference.Reference.Resolve();
        var declaredElement = resolveResult.DeclaredElement;
        if (declaredElement == null)
        {
          return null;
        }

        var declaration = declaredElement.GetDeclarations().FirstOrDefault();

        return declaration != null ? new TreeNode(declaration) : null;
      }
    }

    /// <summary>
    /// Gets the invoked.
    /// </summary>
    public DeclaredElement InvokedElement
    {
      get
      {
        var reference = this.InternalInvocationExpression.InvokedExpression as IReferenceExpression;
        if (reference == null)
        {
          return null;
        }

        var resolveResult = reference.Reference.Resolve();
        var declaredElement = resolveResult.DeclaredElement;

        return declaredElement != null ? new DeclaredElement(declaredElement) : null;
      }
    }

    /// <summary>
    /// Gets the arguments.
    /// </summary>
    [NotNull]
    public IEnumerable<Argument> Arguments
    {
      get
      {
        foreach (var argument in this.InternalInvocationExpression.Arguments)
        {
          yield return new Argument(argument);
        }
      }
    }

    /// <summary>Adds the argument.</summary>
    /// <param name="argument">The argument.</param>
    /// <param name="anchor">The anchor.</param>
    public void AddArgumentAfter([NotNull] Argument argument, [CanBeNull] Argument anchor)
    {
      if (argument == null)
      {
        throw new ArgumentNullException("argument");
      }

      ICSharpArgument a = null;

      if (anchor != null)
      {
        a = anchor.InternalArgument;
      }

      this.InternalInvocationExpression.AddArgumentAfter(argument.InternalArgument, a);
    }

    /// <summary>Adds the argument before.</summary>
    /// <param name="argument">The argument.</param>
    /// <param name="anchor">The anchor.</param>
    public void AddArgumentBefore([NotNull] Argument argument, [CanBeNull] Argument anchor)
    {
      if (argument == null)
      {
        throw new ArgumentNullException("argument");
      }

      ICSharpArgument a = null;

      if (anchor != null)
      {
        a = anchor.InternalArgument;
      }

      this.InternalInvocationExpression.AddArgumentBefore(argument.InternalArgument, a);
    }

    #endregion
  }
}